/*oOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO

Authors: Nitay Megides
		 Rachel Banino

Project: Exercise 3

Using:	hw_code\Hw3Types.h
		hw_code\IspImage.h
		hw_code\Bmp24LineReader.h		

Description:
The Manager is the program. It manages everything, and controls the flow 
of the program.
oOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO*/

#include "Manager.h"
#include <Windows.h>
#include <stdio.h>
#include "Loader.h"
#include "Locator.h"
#include "General.h"
#include <math.h>
#include <stdlib.h>
#include "Comparer.h"
#include "Debug.h"


// Global variable
TPicture GlobalPicture;
TParameters GlobalParameters;

// Local variables
HANDLE ProgramTerminatedEvent;
HANDLE LineReadEvent;
HANDLE ReadingFinished;
Bmp24LineReader LineReader;
HANDLE RunPermissionSemaphore;

BOOL ManagerInitialize(char* bitmapFilename, char* outputFilename, int faceWidth, int faceHeight, int verticalAreasCount, int horizontalAreasCount, int parallelThreads)
{
	int BmpEffectiveWidth = 0;
	int BmpEffectiveHeight = 0;
	int numberOfAreas = verticalAreasCount * horizontalAreasCount;

	double temp = 0;
	int areaIndex = 0;
	int AreaWidth = 0;
	int AreaHeight = 0;
	int LastHeight = 0;
	int LastWidth = 0;
	int x = 0, y = 0;
	PArea currentArea;
	PArea areas = NULL;

	// Initialize everything

	// Initialize the global parameters
	GlobalParameters.FaceWidth = faceWidth;
	GlobalParameters.FaceHeight = faceHeight;	
	GlobalParameters.MaxDiffPixelsBetweenFaces = min(faceWidth + faceHeight, (int)floor((double)(faceHeight * faceWidth) / (double)8));


	areas = (PArea)malloc(sizeof(TArea) * numberOfAreas);
	if (areas == NULL)
	{
		REPORT_ERROR("Manager: Out of memory\n");
		return FALSE;
	}

	if (FileInitialize(outputFilename, "End.txt"))
	{
		REPORT_ERROR("Manager: Error creating log files\n");
		return FALSE;
	}
	

	// Now all the events
	ProgramTerminatedEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
	if (ProgramTerminatedEvent == NULL)
	{
		REPORT_ERROR("Manager: Failed creating ProgramTerminatedEvent\n");
		return FALSE;
	}

	LineReadEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
	if (LineReadEvent == NULL)
	{
		REPORT_ERROR("Manager: Failed creating LineReadEvent\n");
		return FALSE;
	}

	ReadingFinished = CreateEvent(NULL, TRUE, FALSE, NULL);
	if (LineReadEvent == NULL)
	{
		REPORT_ERROR("Manager: Failed creating ReadingFinished event\n");
		return FALSE;
	}

	GlobalPicture.lastReadLineProtector = CreateMutex(NULL, FALSE, NULL);
	if (GlobalPicture.lastReadLineProtector == NULL)
	{
		REPORT_ERROR("Manager: Failed creating Last Read Line Protector\n");
		return FALSE;
	}

	// Our mighty protector
	RunPermissionSemaphore = CreateSemaphore(NULL, parallelThreads, parallelThreads, NULL);
	if (RunPermissionSemaphore == NULL)
	{
		REPORT_ERROR("Manager: Failed creating semaphore\n");
		return FALSE;
	}

	LineReader = OpenBmp24LineReader(bitmapFilename);
	if (!IsInitialized(LineReader))
	{
		REPORT_ERROR("Manager: Failed initializing the BMP line reader\n");
		return FALSE;
	}

	if (!LoaderInitialize(LineReader))
	{
		REPORT_ERROR("Manager: Loader failed to initialize\n");
		return FALSE;
	}

	GlobalPicture.PictureHeight = GetHeight(LineReader);
	GlobalPicture.PictureWidth = GetWidth(LineReader);
	GlobalPicture.Picture = CreateImage(GlobalPicture.PictureHeight, GlobalPicture.PictureWidth);
	if (GlobalPicture.Picture == NULL)
	{
		REPORT_ERROR("Manager: Can't allocate picture (out of memory?)\n");
		return FALSE;
	}
	GlobalPicture.lastReadLine = -1;


	// Calculate the areas

	// Size = (Effective Size) / numberOfAreasHorizontal
	BmpEffectiveWidth = GlobalPicture.PictureWidth - (faceWidth - 1);
	AreaWidth = BmpEffectiveWidth / horizontalAreasCount;
	temp = (BmpEffectiveWidth - (AreaWidth * horizontalAreasCount)) / AreaWidth;
	// Round up or down - To get the minimum difference between area sizes
	if (temp >= 0.5)
		AreaWidth += 1;
	LastWidth = AreaWidth + (BmpEffectiveWidth - horizontalAreasCount * AreaWidth);

	// Same trick for height (NOTICE: The 2 last pixels will never contain a face - As stated on the forum by Amnon)
	BmpEffectiveHeight = GlobalPicture.PictureHeight - (faceHeight - 1) - 2;
	AreaHeight = BmpEffectiveHeight / verticalAreasCount;
	temp = (BmpEffectiveHeight - (AreaHeight * verticalAreasCount)) / AreaHeight;
	if (temp >= 0.5)
		AreaHeight += 1;
	LastHeight = AreaHeight + (BmpEffectiveHeight - verticalAreasCount * AreaHeight);

	// Now Initialize the areas
	for (y = 0; y < verticalAreasCount; y++)
	{
		for (x = 0; x < horizontalAreasCount; x++)
		{
			areaIndex = (y * horizontalAreasCount) + x;
			currentArea = &(areas[areaIndex]);
			currentArea->Index = areaIndex;
			currentArea->X = x * AreaWidth;
			currentArea->Y = y * AreaHeight;
			if (x == horizontalAreasCount - 1)
				currentArea->Width = LastWidth;
			else
				currentArea->Width = AreaWidth;

			if (y == verticalAreasCount - 1)
				currentArea->Height = LastHeight;
			else
				currentArea->Height = AreaHeight;
		}
	}

	// Debug Code (Stays in the release version for testability)
	PrintAreas(areas, verticalAreasCount, horizontalAreasCount);

	// Initialize the locater threads
	if (!LocatorInitialize(areas, horizontalAreasCount * verticalAreasCount))
	{
		REPORT_ERROR("Manager: Locator failed initializing\n");
		return FALSE;
	}

	if (!ComparerInitialize())
	{
		REPORT_ERROR("Manager: Comparer failed to initialize\n");
		return FALSE;
	}

	return TRUE;
}


void ManagerFree()
{
	// Wait until all threads are done
	ComparerFree();
	LocatorFree();
	LoaderFree();

	// Free up resources
	CloseBmp24LineReader(&LineReader);

	CloseHandle(ProgramTerminatedEvent);
	CloseHandle(LineReadEvent);
	CloseHandle(ReadingFinished);
	CloseHandle(RunPermissionSemaphore);
	CloseHandle(GlobalPicture.lastReadLineProtector);

	FreeImage(GlobalPicture.Picture);


	FileFree();
}


BOOL ManagerRun()
{
	// Start everything
	LoaderStartLoading();
	LocatorStartWorking();

	// Wait for the locating to be done
	LocatorWaitForFinish(INFINITE);

	// And start the termination sequence
	REPORT_ERROR("Program Completed Successfully");
	TerminateProgram();

	return TRUE;
}

void NotifyLineRead()
{
	// Notify Line read
	SetEvent(LineReadEvent);
}


void NotifyReadingFinished()
{
	SetEvent(ReadingFinished);
}

BOOL WaitLineRead(const int lineNumber)
{
	int lastLineRead = 0;
	int l_lineNumber = lineNumber;
	HANDLE handles[3];
	DWORD waitResult = 0;

	if (l_lineNumber >= GlobalPicture.PictureHeight)
		l_lineNumber = GlobalPicture.PictureHeight - 1;

	// Carefully get the mutex
	if (!(WaitForSingleObject(GlobalPicture.lastReadLineProtector, INFINITE) == WAIT_OBJECT_0))
	{
		REPORT_ERROR("Manager: WaitForSingleObject Failed!\n");
		return FALSE;
	}
	else
	{
		// Copy the last line read and release the mutex
		lastLineRead = GlobalPicture.lastReadLine;
		ReleaseMutex(GlobalPicture.lastReadLineProtector);
	}

	handles[0] = LineReadEvent;
	handles[1] = ReadingFinished;
	handles[2] = ProgramTerminatedEvent;

	// Check if the line we're waiting for was already read
	while (l_lineNumber > lastLineRead)
	{
		// If not, wait until a new line was read (or the program was terminated)
		// NOTICE: There might be a problem here: The thread could check the while condition
		// And until it gets to the WaitForSingleObject - the object has been set. In that case
		// the thread will wait one or more spare lines. It could happen, and it's not a 
		// big deal.
		waitResult = WaitForMultipleObjects(3, handles, FALSE, INFINITE);
		// Just check if the program was not terminated
		if (waitResult == WAIT_OBJECT_0 + 1)
		{
			// Program was terminated. This is okay
			return TRUE;
		}

		// If the program was not terminated, check that the wait worked
		if (waitResult != WAIT_OBJECT_0)
		{
			// Something went wrong
			return FALSE;
		}


		// Carefully get the mutex (again)
		if (!(WaitForSingleObject(GlobalPicture.lastReadLineProtector, INFINITE) == WAIT_OBJECT_0))
		{
			REPORT_ERROR("Manager: WaitForSingleObject Failed!\n");
			return FALSE;
		}
		else
		{
			// Copy the last line read and release the mutex
			lastLineRead = GlobalPicture.lastReadLine;
			ReleaseMutex(GlobalPicture.lastReadLineProtector);
		}

	}
	return TRUE;
}

BOOL ProgramTerminated()
{
	return (WaitForSingleObject(ProgramTerminatedEvent, 0) == WAIT_OBJECT_0);	
}

BOOL WaitForProgramTerminate(int timeout)
{
	return (WaitForSingleObject(ProgramTerminatedEvent, timeout) == WAIT_OBJECT_0);	
}

void TerminateProgram()
{
	SetEvent(ProgramTerminatedEvent);
	// Free all the waiting threads
	NotifyLineRead();
}

void WaitForRunPermission()
{
	if (!(WaitForSingleObject(RunPermissionSemaphore, INFINITE) == WAIT_OBJECT_0))
	{
		ReportError("Waiting on semaphore failed\n");
	}
}

void ReleaseRunPermission()
{
	ReleaseSemaphore(RunPermissionSemaphore, 1, NULL);
}

